#include "ET++.ph"
#ifdef __GNUG__
#pragma implementation
#endif

#include "TextCmd.h"

#include "Class.h"
#include "TextView.h"
#include "CmdNo.h"
#include "StyledText.h"
#include "RunArray.h"
#include "Math.h"

//---- range functions ---------------------------------------------------------

void CharacterRange(Text*, int at, int *start, int *end)
{
	*start= *end= at;
}

void WordRange(Text *t, int at, int *start, int *end)
{
	t->GetWordRange(at, start, end);
}

void ParagraphRange(Text *t, int at, int *start, int *end)
{
	t->GetParaRange(at, start, end);
}

//---- TextRangeSelector -----------------------------------------------------------

TextRangeSelector::TextRangeSelector(TextView *t, TextRangeFP f)
{
	tv= t;
	range= f;
}

Command *TextRangeSelector::TrackMouse(TrackPhase tp, Point, Point np, Point)
{
	SelPoint nextp;
	int start, end;

	np= np-tv->GetInnerOrigin();

	switch (tp) {
	case eTrackPress:
		tv->DoneTyping();
		tv->inTextSelector= TRUE;
		tv->PointToPoint(np, &nextp.viewp, &nextp.line, &nextp.ch);
		Range(tv->GetText(), nextp.ch, &start, &end);
		DoPress(nextp, start, end);
		break;
		
	case eTrackMove:
		tv->PointToPoint(np, &nextp.viewp, &nextp.line, &nextp.ch);
		Range(tv->GetText(), nextp.ch, &start, &end);
		DoMove(nextp, start, end);
		break;
		
	case eTrackRelease:
		tv->inTextSelector= FALSE;
		if ((tv->start.line == tv->end.line) && tv->Caret())
			tv->InvalidateSelection();
		tv->NormSelection();
		return gNoChanges;
	default:
		break;
	}
	return this;
}

void TextRangeSelector::Range(Text *t, int at, int *from, int *to)
{
	range(t, at, from, to);
	*to= Math::Min(t->End(), *to);
}

void TextRangeSelector::DoPress(SelPoint, int s, int e)
{
	tv->PrivSetSelection(s, e, FALSE);
	startp= tv->start;
	endp= tv->end;
}

void TextRangeSelector::DoMove(SelPoint nextp, int s, int e)
{
	if (nextp.ch <= startp.ch) {
		tv->CharToPoint (nextp.ch= s, &nextp.line, &nextp.viewp);
		if (!nextp.IsEqual(tv->start)) {
			tv->InvalidateRange(nextp.line, nextp.viewp, tv->start.line, tv->start.viewp);
			tv->start= nextp;
		}
		else if (tv->end.ch > endp.ch && !endp.IsEqual(tv->end)) {
			tv->InvalidateRange(endp.line, endp.viewp, tv->end.line, tv->end.viewp);
			tv->end= endp;
		}
	} else {
		tv->CharToPoint(nextp.ch= e, &nextp.line, &nextp.viewp);
		if (!nextp.IsEqual(tv->end)) {
			tv->InvalidateRange(tv->end.line, tv->end.viewp, nextp.line, nextp.viewp);
			tv->end= nextp;
		}
		else if (tv->start.ch < startp.ch && !startp.IsEqual(tv->start)) {
			tv->InvalidateRange(tv->start.line, tv->start.viewp, startp.line, startp.viewp);
			tv->start= startp;
		}
	}
}

//---- ExtendTextRangeSelector -------------------------------------------------

ExtendTextRangeSelector::ExtendTextRangeSelector(TextView *tv, TextRangeFP rf)
														: TextRangeSelector(tv, rf)
{
	swap= FALSE;
}

void ExtendTextRangeSelector::DoPress(SelPoint nextp, int s, int e)
{
	startp.ch= s;
	endp.ch= e;
	if (nextp.ch <= tv->start.ch) {     // extend at start
		tv->CharToPoint(startp.ch, &startp.line, &startp.viewp);
		tv->InvalidateRange(startp.line, startp.viewp, tv->start.line, tv->start.viewp);
		tv->start= startp;
		swap= TRUE;
	} else {                            // extend at end
		tv->CharToPoint(endp.ch, &endp.line, &endp.viewp);
		tv->InvalidateRange(tv->end.line, tv->end.viewp, endp.line, endp.viewp);
		tv->end= endp;
	}
	startp= nextp;
}

void ExtendTextRangeSelector::DoMove(SelPoint nextp, int s, int e)
{
	if (swap) {
		swap= FALSE;
		SwapSelPoints(tv->start, tv->end);
	}
	if (nextp.ch < startp.ch)
		tv->CharToPoint(nextp.ch= s, &nextp.line, &nextp.viewp);
	else
		tv->CharToPoint(nextp.ch= e, &nextp.line, &nextp.viewp);
		
	if (!nextp.IsEqual(tv->end)) {
		tv->InvalidateRange(nextp.line, nextp.viewp, tv->end.line, tv->end.viewp);
		tv->end= nextp;
	}
}

//---- QuickPasteSelector ------------------------------------------------------

QuickPasteSelector::QuickPasteSelector(TextView *tv, int f, int t)
														: TextRangeSelector(tv)
{
	from= f;
	to= t;
}

Command *QuickPasteSelector::TrackMouse(TrackPhase tp, Point ap, Point pp, Point np)
{
	Command *cmd= TextRangeSelector::TrackMouse(tp, ap, pp, np);
	if (tp == eTrackRelease) {
		if (tv->Caret()) {
			tv->SetSelection(from, to);
			return gNoChanges;
		}
		Text *t= tv->SelectionAsText();
		tv->SetSelection(from, to);
		return new PasteTextCommand(tv, t);
	}
	return cmd;
}

//---- DragAndDropSelector -----------------------------------------------------

GrCursor DragAndDropSelector::oldcursor;

DragAndDropSelector::DragAndDropSelector(TextView *tvp, int s, int e, bool p)
{
	dst= from= s;
	to= e;
	tv= tvp;
	moved= FALSE;
	paste= p;
	t= tvp->GetText();
}

Command *DragAndDropSelector::TrackMouse(TrackPhase tp, Point ap, Point, Point np)
{
	delta= np-ap;
	switch (tp) {
	case eTrackPress:
		oldcursor= GrGetCursor();
		break;
	case eTrackMove:
		if (Math::Abs(delta.x) > 4 || Math::Abs(delta.y) > 4) {
			GrSetCursor(eCrsMoveHand);
			moved= TRUE;
		}
		break;
	case eTrackRelease:
		GrSetCursor(oldcursor);
		tv->SetSelection(dst, dst, TRUE);
		if (moved && !(dst >= from && dst < to)) {
			Text *txtsrc= t->Save(from, to);
			tv->SetSelection(dst, dst, TRUE);
			if (paste || tv->DeleteRequest(from, to) == FALSE)
				return new PasteTextCommand(tv, txtsrc);
			return new MoveTextCommand(tv, from, to, dst);
		}
		return gNoChanges;
	default:
		break;
	}
	return this;
}

void DragAndDropSelector::TrackFeedback(Point, Point np, bool)
{
	Point p;
	int line;
	np= np-tv->GetInnerOrigin();
	tv->PointToPoint(np, &p, &line, &dst);
	if (dst < from || dst >= to)
		DrawCaretFeedback(dst);
}

void DragAndDropSelector::DrawCaretFeedback(int at)
{
	int l;
	Point p;
	tv->CharToPoint(at, &l, &p);
	p+= tv->GetInnerOrigin();
	int bh= tv->BaseHeight(l);

	GrSetInk(gInkXor);
	GrMoveto(p);
	GrLineto(Point(p.x, p.y+bh));
}

//----- TextCommand ------------------------------------------------------------

NewAbstractMetaImpl(TextCommand,Command, (TP(tv), TP(oldText), T(oldStart),
																	T(oldEnd)));

TextCommand::TextCommand (TextView *t, int cmdNo, char *cmdName, bool save)
														: Command(cmdNo, cmdName)
{
	tv= t;
	tv->GetSelection(&oldStart, &oldEnd);
	oldText= (Text*)tv->GetText()->New();
	if (save && oldStart != oldEnd) // save old text
		tv->Copy(oldText);
}

TextCommand::~TextCommand ()
{
	SafeDelete(oldText);
}

void TextCommand::RestoreSelection ()
{
	tv->SetSelection(oldStart, oldEnd, FALSE);
}

void TextCommand::RedoIt()
{
	RestoreSelection();
	DoIt();
	tv->RevealSelection();
}

//----- CutCopyTextCommand -----------------------------------------------------

NewMetaImpl0(CutCopyTextCommand, TextCommand);

CutCopyTextCommand::CutCopyTextCommand(TextView *t, int cmdNo, char *cmdName)
									: TextCommand(t, cmdNo, 0, cmdNo != cCOPY)
{
	if (cmdName == 0) {
		if (cmdNo == cCUT)
			cmdName= "Cut Text";
		else if (cmdNo == cCOPY)
			cmdName= "Copy Text";
	}
	SetName(cmdName);
	if (cmdNo == cCOPY)
		SetType(eCmdTypeNoChange);
}

void CutCopyTextCommand::DoIt()
{
	if (GetId() == cCOPY)
		return;
	tv->Cut();
	tv->RevealSelection();
}

void CutCopyTextCommand::UndoIt()
{
	if (GetId() == cCUT) {
		tv->SetSelection(oldStart, oldStart, FALSE);
		if (oldText->End() != 0)
			tv->Paste(oldText);
		RestoreSelection();
		tv->RevealSelection();
	}
}

//----- PasteTextCommand -------------------------------------------------------

NewMetaImpl(PasteTextCommand, TextCommand, (TP(pastetext), T(newStart), T(newEnd)));

PasteTextCommand::PasteTextCommand(TextView *t, Text *pt, int cmdNo, char *cmdName)
						: TextCommand(t, cmdNo, cmdName ? cmdName : "Paste Text")
{
	pastetext= pt;
}

PasteTextCommand::~PasteTextCommand()
{
	SafeDelete(pastetext);
}

void PasteTextCommand::DoIt()
{
	tv->Paste(pastetext);
	tv->GetSelection(&newStart, &newEnd);
	tv->RevealSelection();
}

void PasteTextCommand::UndoIt()
{
	tv->SetSelection(oldStart, newEnd, FALSE);
	tv->Cut();
	if (oldText->End() != 0)
		tv->Paste(oldText);
	RestoreSelection();
	tv->RevealSelection();
}

//---- MoveTextCommand ---------------------------------------------------------

MoveTextCommand::MoveTextCommand(TextView *tview, int f, int t, int d)
												: Command(cPASTE, "Move Text")
{
	tv= tview;
	from= f;
	to= t;
	dst= d;
	mt= 0;
}

MoveTextCommand::~MoveTextCommand()
{
	SafeDelete(mt);
}

void MoveTextCommand::DoIt()
{
	int nd= dst;
	if (dst > from)
		nd= nd-(to-from);
	if (!mt)
		mt= tv->GetText()->Save(from, to);
	tv->SetSelection(from, to, FALSE);
	tv->Cut();
	tv->SetSelection(nd, nd, FALSE);
	tv->Paste(mt);
	tv->SetSelection(nd, nd+to-from, FALSE);
	tv->RevealSelection();
}

void MoveTextCommand::UndoIt()
{
	int nd= dst;
	if (dst > from)
		nd= nd-(to-from);
	tv->SetSelection(nd, nd+to-from, FALSE);
	tv->Cut();
	tv->SetSelection(from, from, FALSE);
	tv->Paste(mt);
	tv->SetSelection(from, to, FALSE);
	tv->RevealSelection();
}

//----- TypingCommand ----------------------------------------------------------

NewMetaImpl(TypingCommand, TextCommand, (TP(backspaceBuf), TP(newText), T(newStart)));

TypingCommand::TypingCommand(TextView *t, int cmdNo, char *cmdName)
											: TextCommand(t, cmdNo, cmdName)
{
	Text *txt= tv->GetText();
	backspaceBuf= (Text*) txt->New();
	newText= (Text*) txt->New();
	newStart= oldStart;
}

TypingCommand::~TypingCommand()
{
	tv->TypingDeleted(this);
	SafeDelete(backspaceBuf);
	SafeDelete(newText);
}

void TypingCommand::UndoIt()
{
	Text *t= tv->GetText();
	int firstNewChar;

	firstNewChar= Math::Min(oldStart - backspaceBuf->End(), oldStart);
	if (firstNewChar != newStart) {                     // save new text
		t->Copy(newText, firstNewChar, newStart);
		tv->SetSelection(firstNewChar, newStart, TRUE);
		tv->Cut();                                      // remove new text
	} else
		tv->SetSelection(firstNewChar, newStart, TRUE);
	if (backspaceBuf->End() != 0)
		tv->Paste(backspaceBuf);                        // insert saved text
	if (oldText->End() != 0)
		tv->Paste(oldText);
	RestoreSelection();
	tv->RevealSelection();
}

void TypingCommand::RedoIt()
{
	RestoreSelection();
	tv->Cut();
	if (backspaceBuf->End() != 0) {
		tv->SetSelection(oldStart - backspaceBuf->End(), oldStart, FALSE);
		tv->Cut();
	}
	if (newText->End() != 0)
		tv->Paste(newText);
	tv->RevealSelection();
}

void TypingCommand::AddChar(int n)
{
	newStart+= n;
}

void TypingCommand::DelChar()
{
	if (newStart > 0)
		newStart--;
	if (newStart < oldStart
		&& oldStart - newStart > backspaceBuf->End())   // char already saved??
			backspaceBuf->ReplaceRange(0, 0, tv->GetText(), newStart, newStart+1);
}

//---- CharStyleCommand --------------------------------------------------------

NewMetaImpl(CharStyleCommand, TextCommand, (TP(newStyles), TP(oldStyles), TP(tvp), TE(mode)));

CharStyleCommand::CharStyleCommand(TextView *t, int cmdNo, char *cmdName,
			TxtCharProp m, const CharStyleSpec &ns)
										: TextCommand(t, cmdNo, cmdName, FALSE)
{
	st= Guard(t->GetText(), StyledText);
	newStyles= oldStyles= 0;
	tvp= t;
	style= ns;
	mode= m;
}

CharStyleCommand::~CharStyleCommand()
{
	SafeDelete(oldStyles);
	SafeDelete(newStyles);
}

void CharStyleCommand::DoIt()
{
	int from, to;

	tvp->GetSelection(&from, &to);
	if (tvp->Caret())
		st->SetCharStyle(mode, from, to, style);
	else {
		if (oldStyles == 0)
			oldStyles= new RunArray;
		st->GetCharStyles()->Copy(oldStyles, from, to);
		st->SetCharStyle(mode, from, to, style);
	}
	tvp->RevealSelection();
}

void CharStyleCommand::UndoIt()
{
	if (oldStart == oldEnd)
		st->ResetCurrentCharStyle();
	else {
		if (newStyles == 0) {
			newStyles= new RunArray;
			st->GetCharStyles()->Copy(newStyles, oldStart, oldEnd);
		}
		st->ReplaceCharStyles(oldStyles, oldStart, oldEnd);
	}
	RestoreSelection();
	tvp->RevealSelection();
}

void CharStyleCommand::RedoIt()
{
	if (oldStart == oldEnd)
		st->SetCharStyle(mode, oldStart, oldEnd, style);
	else
		st->ReplaceCharStyles(newStyles, oldStart, oldEnd);
	RestoreSelection();
	tvp->RevealSelection();
}


//---- ParaStyleCommand --------------------------------------------------------

NewMetaImpl(ParaStyleCommand,TextCommand, (TP(newStyles), TP(oldStyles),
																	TP(tvp)));

ParaStyleCommand::ParaStyleCommand(TextView *t, int cmdNo, char *cmdName,
									TxtParaProp p, const ParaDesc &newStyle)
										: TextCommand(t, cmdNo, cmdName, FALSE)
{
	st= Guard(t->GetText(), StyledText);
	newStyles= oldStyles= 0;
	tvp= t;
	mode= p;
	style= newStyle;
	notify= 0;
}

ParaStyleCommand::~ParaStyleCommand()
{
	SafeDelete(oldStyles);
	SafeDelete(newStyles);
	if (notify)
		notify->Control(cIdNone, cPartCommandDone, this);
}

void ParaStyleCommand::DoIt()
{
	int from, to, dd;

	tvp->GetSelection(&from, &to);
	st->GetParaRange(from, &oldStart, &dd);
	if (to == from)
		oldEnd= dd;
	else if (st->IsPara((*st)[to-1]))
		oldEnd= to;
	else
		st->GetParaRange(to, &dd, &oldEnd);
	if (oldStyles == 0) {
		oldStyles= new RunArray;
		st->GetParaStyles()->Copy(oldStyles, oldStart, oldEnd);
	}
	st->SetParaStyle(mode, oldStart, oldEnd, style);
	tvp->RevealSelection();
}

void ParaStyleCommand::UndoIt()
{
	if (newStyles == 0) {
		newStyles= new RunArray;
		st->GetParaStyles()->Copy(newStyles, oldStart, oldEnd);
	}
	st->ReplaceParaStyles(oldStyles, oldStart, oldEnd);
	RestoreSelection();
	tvp->RevealSelection();
}

void ParaStyleCommand::RedoIt()
{
	st->ReplaceParaStyles(newStyles, oldStart, oldEnd);
	RestoreSelection();
	tvp->RevealSelection();
}

void ParaStyleCommand::Apply(TxtParaProp p, const ParaDesc &newStyle)
{
	mode= p;
	style= newStyle;
}

void ParaStyleCommand::NotifyWhenDone(EvtHandler *evt)
{
	notify= evt;
}

TxtParaProp ParaStyleCommand::GetNewStyle(ParaDesc &newStyle)
{
	newStyle= style;
	return mode;
}

